<?php
/**
* phpCan - http://idc.anavallasuiza.com/
*
* phpCan is released under the GNU Affero GPL version 3
*
* More information at license.txt
*/

namespace files\css;

defined('ANS') or die();

class Css {
	private $code = array();
	private $vars = array();
	private $Debug;
	private $file;

	public $settings = array();


	/**
	 * public function __construct ()
	 */
	function __construct () {
		global $Debug;

		$this->Debug = $Debug;
	}


	/**
	 * public function setSettings (array $settings = null)
	 *
	 * return boolean
	 */
	public function setSettings ($settings = null) {
		global $Config;

		if (is_null($settings)) {
			$settings = 'css';
		}

		if (is_string($settings) && $Config->config[$settings]) {
			$this->settings = $Config->config[$settings];
		} else if (is_array($settings)) {
			$this->settings = $settings;
		} else {
			return false;
		}

		$this->vars = array();

		//Prepare vars
		if (is_array($this->settings['vars'])) {
			foreach ($this->settings['vars'] as $search => $replace) {
				$this->vars['search'][] = '/'.preg_quote('$'.$search).'([^\w-]|$)/';
				$this->vars['replace'][] = $replace.'$1';
			}
		}
	}


	/**
	 * public function load (string $file)
	 *
	 * Load a css file and parse it
	 *
	 * return boolean
	 */
	public function load ($file) {
		$this->file = '';

		if (!is_file($file)) {
			echo __('"%s" does not exists', $file);
			die();
		}

		$code = file_get_contents($file);

		$this->parse($code);

		$this->file = $file;

		return $this;
	}


	/**
	 * private function parse (string $code)
	 *
	 * return this
	 */
	private function parse ($code) {
		$this->code = array();

		//Remove comments
		$code = preg_replace('|/\*.*\*/|Us', '', $code);

		//Remove spaces
		$code = preg_replace('/(\s{2,}|\r|\n)/', ' ', $code);

		//Parse code to $this->code
		preg_replace_callback('/([^\{]*)\{([^\}]*)\}/', array($this, 'parse_code'), $code);

		return $this;
	}


	/**
	 * private function parse_code (array $code)
	 *
	 * return none
	 */
	private function parse_code ($code) {
		static $num = 0;

		$selector = trim($num.' '.$code[1]);
		$styles = explode(';', $code[2]);
		$num++;

		foreach ($styles as $style) {
			if (!trim($style)) {
				continue;
			}

			$style = explode(':', $style, 2);

			preg_match('/^(\(([^\)]*)\))?([^(]+)$/', trim($style[0]), $matches);

			$param = trim($matches[3]);
			$value = trim($style[1]);

			//Replace variables
			if (strpos($value, '$') !== FALSE && $this->vars) {
				$value = preg_replace($this->vars['search'], $this->vars['replace'], $value);
			}

			//Paths relative to css file
			if (strpos($value, 'url') !== FALSE) {
				$value = preg_replace('#url\s*\(["\']?([^\)\'"]*)["\']?\)#', 'url(\''.$this->settings['www_path'].'\1\')', $value);
				$value = preg_replace('#/\w+/\.\./#', '/', $value);
			}

			//Crossbrowser params
			if ($param[0] == '$') {
				$param = substr($param, 1);

				switch ($param) {
					//-moz-, -webkit- and -o-
					case 'background-size':
					case 'transition':
						$this->code[$selector][] = $this->addVendorPrefixes($value, $param, array('moz', 'webkit', 'o'));
						break;
					
					//-moz- and -webkit-
					case 'border-image':
					case 'border-top-image':
					case 'border-right-image':
					case 'border-bottom-image':
					case 'border-left-image':
					case 'border-corner-image':
					case 'border-top-left-image':
					case 'border-top-right-image':
					case 'border-bottom-left-image':
					case 'border-bottom-right-image':
					case 'border-radius':
					case 'box-shadow':
					case 'box-sizing':
					case 'box-orient':
					case 'box-flex':
					case 'box-align':
					case 'box-direction':
					case 'box-flex-group':
					case 'box-lines':
					case 'box-ordinal-group':
					case 'box-pack':
					case 'column-count':
					case 'column-gap':
					case 'column-rule':
					case 'column-width':
						$this->code[$selector][] = $this->addVendorPrefixes($value, $param, array('moz', 'webkit'));
						break;

					//-moz-, -webkit-, -o- and -ms-
					case 'transform':
					case 'transform-origin':
						$this->code[$selector][] = $this->addVendorPrefixes($value, $param, array('moz', 'webkit', 'o', 'ms'));
						break;

					//-ms-
					case 'filter':
						$this->code[$selector][] = $this->addVendorPrefixes($value, $param, array('ms'));
						break;

					//border radius (-moz- and -webkit-)
					case 'border-top-right-radius':
					case 'border-top-left-radius':
					case 'border-bottom-right-radius':
					case 'border-bottom-left-radius':
						$this->code[$selector][] = $this->borderRadius($value, $param);
						break;

					//Values with prefix (-moz- and -webkit-)
					case 'display':
						foreach ($this->addValueVendorPrefixes($value, $param, array('moz', 'webkit')) as $v) {
							$this->code[$selector][] = $v;
						}
						break;

					//special effects
					case 'opacity':
						$this->code[$selector][] = $this->opacity($value);
						break;

					case 'background-linear-gradient':
						foreach ($this->backgroundLinearGradient($value) as $v) {
							$this->code[$selector][] = $v;
						}
						break;

					case 'background-rgba':
						$this->code[$selector][] = $this->backgroundRgba($value);
						break;

					case 'flip':
						$this->code[$selector][] = $this->flip($value);
						break;

					case 'rotate':
						$this->code[$selector][] = $this->rotate($value);
						break;
				}
			} else {
				$this->code[$selector][][$param] = $value;
			}
		}
	}


	/**
	 * private function addVendorPrefixes (string $value, string $param, $prefixes)
	 *
	 * Return array
	 */
	private function addVendorPrefixes ($value, $param, $prefixes) {
		$return = array();

		foreach ($prefixes as $prefix) {
			$return['-'.$prefix.'-'.$param] = $value;
		}

		$return[$param] = $value;

		return $return;
	}


	/**
	 * private function addValueVendorPrefixes (string $value, string $param, $prefixes)
	 *
	 * Return array
	 */
	private function addValueVendorPrefixes ($value, $param, $prefixes) {
		$return = array();

		foreach ($prefixes as $prefix) {
			$return[$prefix] = array($param => '-'.$prefix.'-'.$value);
		}

		return $return;
	}


	/**
	 * private function opacity (string $value)
	 *
	 * Return array
	 */
	private function opacity ($value) {
		$return = $this->addVendorPrefixes($value, 'opacity', array('moz', 'webkit'));

		$ie_value = $value * 100;

		$return['-ms-filter'] = 'progid:DXImageTransform.Microsoft.Alpha(Opacity='.$ie_value.')';
		$return['filter'] = 'alpha(opacity='.$ie_value.')';

		return $return;
	}


	/**
	 * private function backgroundLinearGradient (string $value)
	 *
	 * Return array
	 */
	private function backgroundLinearGradient ($value) {
		list($type, $start, $end) = explode(' ', $value);

		$start = trim($start);
		$end = trim($end);

		if (trim($type) == 'top') {
			return array(
				'ms' => $this->addVendorPrefixes("progid:DXImageTransform.Microsoft.gradient(startColorStr='$start', endColorStr='$end')", 'filter', array('ms')),
				'webkit' => array('background' => "-webkit-gradient(linear, left top, left bottom, from($start), to($end)) no-repeat $end"),
				'moz' => array('background' => "-moz-linear-gradient(top, $start, $end) no-repeat $end"),
				'o' => array('background' => "-o-linear-gradient(top, $start, $end) no-repeat $end"),
				'w3c' => array('background' => "linear-gradient(top, $start, $end) no-repeat $end")
			);
		}

		return array(
			'ms' => $this->addVendorPrefixes("progid:DXImageTransform.Microsoft.gradient(startColorStr='$start', endColorStr='$end', GradientType=1)", 'filter', array('ms')),
			'webkit' => array('background' => "-webkit-gradient(linear, left top, right top, from($start), to($end)) no-repeat"),
			'moz' => array('background' => "-moz-linear-gradient(left, $start, $end) no-repeat"),
			'o' => array('background' => "-o-linear-gradient(left, $start, $end) no-repeat"),
			'w3c' => array('background' => "linear-gradient(left, $start, $end) no-repeat")
		);
	}


	/**
	 * private function backgroundRgba (string $value)
	 *
	 * Return array
	 */
	private function backgroundRgba ($value) {
		list($r, $g, $b, $a) = explode(' ', $value);

		$r = intval($r);
		$g = intval($g);
		$b = intval($b);
		$a = floatval($a);

		$msr = dechex($r);
		$msg = dechex($g);
		$msb = dechex($b);

		if (strlen($msr) == 1) {
			$msr += $msr;
		}
		if (strlen($msg) == 1) {
			$msg += $msg;
		}
		if (strlen($msb) == 1) {
			$msb += $msb;
		}

		$msa = dechex(round(255*$a));

		$color = '#'.$msa.$msr.$msg.$msb;

		$return = $this->addVendorPrefixes("progid:DXImageTransform.Microsoft.gradient(startColorStr='$color', endColorStr='$color')", 'filter', array('ms'));
		$return['background'] = "rgba($r, $g, $b, $a)";

		return $return;
	}


	/**
	 * private function flip (string $value)
	 *
	 * Return array
	 */
	private function flip ($value) {
		if (trim($value) == 'h') {
			$return = $this->addVendorPrefixes('scaleX(-1)', 'transform', array('moz', 'webkit', 'o'));
			$return += $this->addVendorPrefixes('flipH', 'filter', array('ms'));
		} else {
			$return = $this->addVendorPrefixes('scaleY(-1)', 'transform', array('moz', 'webkit', 'o'));
			$return += $this->addVendorPrefixes('flipV', 'filter', array('ms'));
		}

		return $return;
	}


	/**
	 * private function rotate (string $value)
	 *
	 * Return array
	 */
	private function rotate ($value) {
		$value = intval($value);

		switch ($value) {
			case 90:
			$ms = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
			break;

			case 180:
			$ms = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';

			case 270:
			$ms = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';

			case 360:
			$ms = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=4)';

			default:
			$rad = ($value * pi() * 2) / 360;
			$cos = cos($rad);
			$sin = sin($rad);
			$ms = 'progid:DXImageTransform.Microsoft.Matrix(sizingMethod="auto expand", M11 = '.$cos.', M12 = '.(-$sin).', M21 = '.$sin.', M22 = '.$cos.')';
		}

		$return = $this->addVendorPrefixes('rotate('.$value.'deg)', 'transform', array('moz', 'webkit', 'o'));
		$return += $this->addVendorPrefixes($ms, 'filter', array('ms'));
		return $return;
	}


	/**
	 * private function borderRadius (string $value, string $param)
	 *
	 * Return array
	 */
	private function borderRadius ($value, $param) {
		$return = $this->addVendorPrefixes($value, $param, array('webkit'));

		switch ($param) {
			case 'border-top-right-radius':
				$return['-moz-border-radius-topright'] = $value;
				return $return;

			case 'border-top-left-radius':
				$return['-moz-border-radius-topleft'] = $value;
				return $return;

			case 'border-bottom-right-radius':
				$return['-moz-border-radius-bottomright'] = $value;
				return $return;

			case 'border-bottom-left-radius':
				$return['-moz-border-radius-bottomleft'] = $value;
				return $return;
		}
	}


	/**
	 * public function show ([bool $header])
	 *
	 * Print the css file
	 */
	public function show ($header = true) {
		if ($header) {
			header('Content-type: text/css');

			if ($this->settings['cache']) {
				header('Expires: '.gmdate('D, d M Y H:i:s',(time() + $this->settings['cache']).' GMT'));
			}
		}

		//Get text
		$text = $this->get();

		//Save cache
		if ($this->settings['cache']) {
			$Cache = new \Cache;

			$Cache->save($this->file, $text, 'css');
		}

		echo $text;

		die();
	}


	/**
	 * public function showCached (string $file, [bool $header])
	 *
	 * Print the cached css file
	 *
	 * return boolean
	 */
	public function showCached ($file, $header = true) {
		if (!$this->settings['cache']) {
			return false;
		}

		$Cache = new \Cache;

		if ($text = $Cache->load($file, $this->settings['cache'], 'css')) {
			if ($header) {
				header('Content-type: text/css');
				header('Expires: '.gmdate('D, d M Y H:i:s',(time() + $this->settings['cache'])).' GMT');
			}

			echo $text;

			die();
		}

		return false;
	}


	/**
	 * public function get ()
	 *
	 * Return transformed text
	 *
	 * return string
	 */
	public function get () {
		$text = '';

		//Get text
		foreach ($this->code as $selector => $styles) {
			$selector = trim(strstr($selector, ' '));
			$text .= $selector.' {'."\n";

			foreach ($styles as $values) {
				foreach ($values as $param => $value) {
					$text .= $param.':'.$value.';'."\n";
				}
			}

			$text .= '}'."\n";
		}

		return $text;
	}
}
?>
